﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Calhoon.MarkupParser;

namespace WhiteListExample
{
	public partial class WhiteListTagConverter : TagConverter
	{
		private HtmlTagDefinitions mHtmlTagDefinitions;
		public WhiteListTagConverter() : base() {
			LoadHtmlTagDefinitions();
		}

		protected void LoadHtmlTagDefinitions() {
			mHtmlTagDefinitions = new HtmlTagDefinitions();
			Regex matchAnythingRegex = new Regex(@".*", RegexOptions.Compiled);
			Regex numberRegex = new Regex(@"^[0-9]?[0-9]?[0-9]?[0-9]?( )*((px)|(%)|(em))?$", RegexOptions.Compiled);

			mHtmlTagDefinitions
				.AddTag("a", false)
					.AddWhiteListedAttribute("href", matchAnythingRegex)
					.AddWhiteListedAttribute("title", matchAnythingRegex)

				.AddTag("b", false)
				.AddTag("blockquote", false)
				.AddTag("br", true)
				.AddTag("code", false)
				.AddTag("del", false)
				.AddTag("div", false)
				.AddTag("dd", false)
				.AddTag("dl", false)
				.AddTag("dt", false)
				.AddTag("em", false)
				.AddTag("h1", false)
				.AddTag("h2", false)
				.AddTag("h3", false)
				.AddTag("h4", false)
				.AddTag("h5", false)
				.AddTag("h6", false)
				.AddTag("hr", true)
				.AddTag("i", false)
				.AddTag("img", true)
					.AddWhiteListedAttribute("src", new Regex(@".*", RegexOptions.Compiled))
					.AddWhiteListedAttribute("alt", matchAnythingRegex)
					.AddWhiteListedAttribute("width", numberRegex)
					.AddWhiteListedAttribute("height", numberRegex)
					.AddWhiteListedAttribute("title", matchAnythingRegex)

				.AddTag("kbd", false)
				.AddTag("p", false)
				.AddTag("pre", false)
				.AddTag("s", false)
				.AddTag("span", false)
				.AddTag("sup", false)
				.AddTag("sub", false)
				.AddTag("strong", false)
				.AddTag("strike", false)
				.AddTag("table", false)
					.AddWhiteListedAttribute("cellpadding", numberRegex)
					.AddWhiteListedAttribute("cellspacing", numberRegex)
					.AddWhiteListedAttribute("border", numberRegex)

				.AddTag("tr", false)
				.AddTag("td", false)
				.AddTag("th", false)

				.AddTag("ul", false)
				.AddTag("li", false)
				.AddTag("ol", false);
		}

		protected override HierarchyNode DefaultGetHierarchyNode(Tag tag) {
			if (mHtmlTagDefinitions.ContainsKey(tag.TagName)) {
				HierarchyNode node = new HierarchyNode();
				node.TagName = tag.TagName;

				foreach (KeyValuePair<string, AttributeValue> attribute in tag.Attributes) {
					if (mHtmlTagDefinitions[node.TagName].AttributeWhiteList.ContainsKey(attribute.Key)
							&& mHtmlTagDefinitions[node.TagName].AttributeWhiteList[attribute.Key].IsMatch(attribute.Value.Value)) {
						if (attribute.Value.Value.Contains('\'')) {
							if (attribute.Value.Value.Contains('"')) {
								//If the attribute already contains both " and '.
								node.Attributes.Add(attribute.Key, new AttributeValue(attribute.Value.Value.Replace('"', '\''), AttributeValueDelimiterType.DoubleQuote));
							}
							else {
								//Attribute contains ' but not "
								node.Attributes.Add(attribute.Key, new AttributeValue(attribute.Value.Value, AttributeValueDelimiterType.DoubleQuote));
							}
						}
						else {
							//Attribute contains " but not '
							node.Attributes.Add(attribute.Key, new AttributeValue(attribute.Value.Value, AttributeValueDelimiterType.Quote));
						}
					}
				}

				return node;
			}
			else {
				return null;
			}
		}

		protected override bool DefaultIsSelfClosing(Tag tag) {
			if (mHtmlTagDefinitions.ContainsKey(tag.TagName)) {
				return mHtmlTagDefinitions[tag.TagName].IsSelfClosing;
			}
			else {
				return base.DefaultIsSelfClosing(tag);
			}
		}
	}

	//Helper classes/structures to create cleaner syntax when creating the whitelist.
	class HtmlTagDefinitions : Dictionary<string, HtmlTagDefinition>
	{
		public HtmlTagDefinitions() : base(StringComparer.OrdinalIgnoreCase) { }

		private string mLastKey;
		public HtmlTagDefinitions AddTag(string tagName, bool isSelfClosing) {
			base.Add(tagName, new HtmlTagDefinition(isSelfClosing));
			mLastKey = tagName;

			return this;
		}

		public HtmlTagDefinitions AddWhiteListedAttribute(string name, Regex inputRegex) {
			base[mLastKey].AttributeWhiteList.Add(name, inputRegex);
			return this;
		}
	}

	struct HtmlTagDefinition
	{
		private bool mIsSelfClosing;
		public bool IsSelfClosing {
			get {
				return mIsSelfClosing;
			}
		}

		private Dictionary<string, Regex> mAttributeWhiteList;
		public Dictionary<string, Regex> AttributeWhiteList {
			get {
				return mAttributeWhiteList;
			}
		}

		public HtmlTagDefinition(bool IsSelfClosing) {
			this.mIsSelfClosing = IsSelfClosing;
			this.mAttributeWhiteList = new Dictionary<string, Regex>(StringComparer.OrdinalIgnoreCase);
		}
	}
}
